Appearance
《C++面向对象程序设计(甲)》期末试卷B (精选04)
一、单项选择题(每题 2 分,总计 20 分)
- 面向对象程序设计中的数据隐藏指的是( )
- A. 输入数据必须输入保密口令
- B. 数据经过加密处理
- C. 对象内部数据和代码合并在一起
- D. 对象内部数据结构的不可访问性
查看答案与解析
答案:D
解析: 数据隐藏(Data Hiding)是面向对象封装性的核心概念,指对象内部的数据结构对外部是不可访问的,外部只能通过对象提供的公有接口(成员函数)来操作数据。
- A:❌ 保密口令是安全机制,不是数据隐藏
- B:❌ 加密处理是数据安全手段,不是数据隐藏
- C:❌ 数据与代码合并是封装的表现形式,但不是数据隐藏的定义
- D:✅ 数据隐藏的本质是内部数据结构对外不可见
难度: ⭐ 考点: #数据隐藏 #封装性 #面向对象
💡 学习锦囊
📖 相关公式与知识点:
- 封装 = 数据隐藏 + 公有接口
- 访问控制:private 成员对外隐藏,public 成员对外可见
- 数据隐藏提高了代码的安全性和可维护性
易错点
- 数据隐藏 ≠ 数据加密
- 封装不仅包括数据隐藏,还包括将数据与操作绑定在一起
🔄 举一反三
- 下列关于封装的描述,错误的是( )
- A. 封装将数据与操作数据的函数绑定在一起
- B. 封装通过访问控制实现数据隐藏
- C. 封装意味着所有数据都必须加密存储
- D. 封装提高了代码的可维护性
查看练习答案与解析
答案:C 解析:封装不要求数据加密,而是通过 private 等访问控制实现数据隐藏。
- 一个内联函数 Fun,使用 int 类型的参数,求其平方并返回,返回值也为 int 类型,下列定义正确的是( )
- A.
int Fun(int x){return x*x;} - B.
inline int Fun(int x){return x*x;} - C.
int Fun inline(int x){return x*x;} - D.
int Fun(int x){inline return x*x;}
- A.
查看答案与解析
答案:B
解析: 内联函数的正确声明格式:inline 返回类型 函数名(参数列表) { 函数体 }
- A:❌ 缺少
inline关键字,是普通函数 - B:✅ 标准的内联函数定义格式
- C:❌
inline放在函数名和参数列表之间,语法错误 - D:❌
inline不能放在 return 语句前
难度: ⭐ 考点: #内联函数 #inline关键字
💡 学习锦囊
📖 相关公式与知识点:
- 内联函数声明:
inline 返回类型 函数名(参数) { 函数体 } - 类体内定义的成员函数自动成为内联函数
inline是建议,编译器可能忽略
易错点
inline必须放在返回类型之前- 类体外定义的成员函数需要显式加
inline
🔄 举一反三
- 下列内联函数定义正确的是( )
- A.
inline int max(int a, int b) { return a > b ? a : b; } - B.
int max inline(int a, int b) { return a > b ? a : b; } - C.
int max(int a, int b) inline { return a > b ? a : b; } - D.
int max(inline int a, inline int b) { return a > b ? a : b; }
查看练习答案与解析
答案:A 解析:
inline必须在返回类型之前。B 中inline放在函数名和参数之间,C 中inline放在函数体前,D 中inline放在参数前,均为语法错误。 - A.
- 下面关于重载函数的叙述中正确的是( )
- A. 重载函数必须具有不同的返回值类型
- B. 重载函数的形参个数必须不同
- C. 重载函数必须有不同的形参列表
- D. 重载函数的函数名可以不同
查看答案与解析
答案:C
解析: 函数重载的核心规则:同名函数,参数列表必须不同(参数个数、类型或顺序不同)。
- A:❌ 返回值类型不同不能构成重载
- B:❌ 形参个数可以相同,只要类型不同即可
- C:✅ 重载函数必须有不同的形参列表
- D:❌ 重载函数的函数名必须相同
void func(int a); // 1
void func(double a); // 2 - 参数类型不同,合法重载
void func(int a, int b); // 3 - 参数个数不同,合法重载
int func(int a); // ❌ 仅返回值不同,不合法2
3
4
难度: ⭐ 考点: #函数重载 #重载规则
💡 学习锦囊
📖 相关公式与知识点:
- 重载条件:函数名相同,参数列表不同
- 参数列表不同:个数不同、类型不同、顺序不同
- 返回值类型、默认参数不参与重载区分
易错点
- 仅返回值类型不同不能构成重载
- 仅默认参数不同不能构成重载
🔄 举一反三
- 下列函数声明中,能与
void func(int a)构成重载的是( )- A.
int func(int a) - B.
void func(int b) - C.
void func(double a) - D.
void func(int a, int b = 0)
查看练习答案与解析
答案:C 解析:A 仅返回值不同,B 参数完全相同(形参名不影响),C 参数类型不同,D 默认参数不参与重载区分。
- A.
- 下列关于纯虚函数的描述中,错误的是( )
- A. 在基类中只有声明,通常没有函数体定义
- B. 可以使用包含纯虚函数的类来创建对象
- C. 当需要使用包含纯虚函数的基类的派生类创建对象时,须在派生类中给出该函数定义
- D. 包含纯虚函数的类称为抽象类
查看答案与解析
答案:B
解析: 包含纯虚函数的类是抽象类,不能创建对象。
- A:✅ 纯虚函数在基类中通常只声明不定义(C++ 允许在类外为纯虚函数提供定义,但通常不提供)
- B:❌ 抽象类不能实例化,这是错误的说法
- C:✅ 派生类必须实现纯虚函数才能创建对象
- D:✅ 含纯虚函数的类称为抽象类
难度: ⭐ 考点: #纯虚函数 #抽象类
💡 学习锦囊
📖 相关公式与知识点:
- 纯虚函数:
virtual 返回类型 函数名(参数) = 0; - 抽象类:包含至少一个纯虚函数的类
- 抽象类不能创建对象,但可以定义指针和引用
易错点
- 抽象类不能实例化,这是最常见的考点
- 抽象类可以有构造函数(供派生类调用)
🔄 举一反三
- 关于抽象类,下列说法正确的是( )
- A. 抽象类不能有构造函数
- B. 抽象类不能有数据成员
- C. 抽象类不能创建对象
- D. 抽象类不能定义指针
查看练习答案与解析
答案:C 解析:抽象类可以有构造函数和数据成员,可以定义指针和引用,但不能创建对象。
- 下列关于析构函数的描述中正确的是( )
- A. 析构函数可以重载
- B. 析构函数可以是虚函数
- C. 析构函数名与类名相同
- D. 析构函数的返回类型为 void
查看答案与解析
答案:B
解析:
- A:❌ 析构函数不能重载(无参数,只能有一个)
- B:✅ 析构函数可以是虚函数,且基类析构函数通常应声明为虚函数
- C:❌ 析构函数名是
~类名,不是与类名完全相同 - D:❌ 析构函数没有返回类型(连 void 也不能写)
class Base {
public:
virtual ~Base() {} // 虚析构函数
};2
3
4
难度: ⭐⭐ 考点: #析构函数 #虚析构函数
💡 学习锦囊
📖 相关公式与知识点:
- 析构函数:
~类名(),无参数、无返回值、不可重载 - 虚析构函数:基类析构函数声明为 virtual,确保派生类对象正确析构
- 经验法则:有虚函数的基类应声明虚析构函数
易错点
- 析构函数名是
~类名,不是与类名完全相同 - 析构函数没有返回类型,void 也不能写
🔄 举一反三
- 若基类析构函数不是虚函数,
delete基类指针指向派生类对象时会发生什么?( )- A. 正常析构派生类和基类部分
- B. 只析构基类部分,派生类部分泄漏
- C. 编译错误
- D. 只析构派生类部分
查看练习答案与解析
答案:B 解析:非虚析构函数下,
delete基类指针只调用基类析构函数,派生类部分资源无法释放。
- 下列关于纯虚函数的描述中,正确的是( )
- A. 纯虚函数是一种特殊的虚函数,它是个空函数
- B. 具有纯虚函数的类称为虚基类
- C. 一个基类中说明有纯虚函数,其派生类一定要实现该纯虚函数
- D. 具有纯虚函数的类不能创建类对象
查看答案与解析
答案:D
解析:
- A:❌ 纯虚函数不一定是空函数,可以有默认实现
- B:❌ 具有纯虚函数的类称为抽象类,不是虚基类
- C:❌ 派生类可以不实现纯虚函数,此时派生类也是抽象类
- D:✅ 抽象类不能创建对象
难度: ⭐ 考点: #纯虚函数 #抽象类
💡 学习锦囊
📖 相关公式与知识点:
- 抽象类 ≠ 虚基类(虚基类用于解决菱形继承问题)
- 派生类可以不实现纯虚函数,但此时派生类仍是抽象类
- 纯虚函数可以有函数体:
virtual void f() = 0 { /* ... */ }
易错点
- "虚基类"和"抽象类"是两个完全不同的概念
- 派生类不是"一定要"实现纯虚函数
🔄 举一反三
- 若类 B 继承自抽象类 A 但不实现 A 的纯虚函数,则( )
- A. 编译错误
- B. B 也是抽象类,不能创建 B 的对象
- C. B 可以创建对象,调用纯虚函数时崩溃
- D. B 自动成为普通类
查看练习答案与解析
答案:B 解析:派生类不实现纯虚函数时,派生类仍是抽象类,不能创建对象。
- 复制初始化构造函数的作用是( )
- A. 进行数据类型的转换
- B. 用对象调用成员函数
- C. 用对象初始化对象
- D. 用一般类型的数据初始化对象
查看答案与解析
答案:C
解析: 复制初始化构造函数(拷贝构造函数)的作用是用已有对象初始化新创建的对象。
MyClass obj1;
MyClass obj2(obj1); // 拷贝构造:用 obj1 初始化 obj2
MyClass obj3 = obj1; // 拷贝构造:用 obj1 初始化 obj32
3
难度: ⭐ 考点: #拷贝构造函数 #对象初始化
💡 学习锦囊
📖 相关公式与知识点:
- 拷贝构造函数:
类名(const 类名 &obj) - 调用时机:①用对象初始化对象 ②值传递 ③返回对象
- 默认拷贝构造函数执行浅拷贝
易错点
- 拷贝构造是"初始化"新对象,不是"赋值"已有对象
obj2 = obj1是赋值操作,不调用拷贝构造函数
🔄 举一反三
- 下列哪种情况会调用拷贝构造函数?( )
- A.
MyClass a; MyClass b; b = a; - B.
MyClass a; MyClass b(a); - C.
MyClass *p = new MyClass; - D.
MyClass a; MyClass &b = a;
查看练习答案与解析
答案:B 解析:A 是赋值操作,B 是拷贝构造,C 是普通构造,D 是引用不调用任何构造函数。
- A.
- 所谓多态性是指( )
- A. 不同的对象调用不同名称的函数
- B. 不同的对象调用相同名称的函数
- C. 一个对象调用不同名称的函数
- D. 一个对象调用不同名称的对象
查看答案与解析
答案:B
解析: 多态性(Polymorphism)的核心含义:不同的对象在接收到相同名称的函数调用时,表现出不同的行为。
class Shape {
public:
virtual void draw() = 0;
};
class Circle : public Shape {
void draw() { /* 画圆 */ }
};
class Square : public Shape {
void draw() { /* 画方 */ }
};
// 不同对象调用同名函数 draw(),表现不同行为2
3
4
5
6
7
8
9
10
11
难度: ⭐ 考点: #多态性 #虚函数
💡 学习锦囊
📖 相关公式与知识点:
- 多态 = 同一接口,不同实现
- 静态多态:函数重载、模板(编译时)
- 动态多态:虚函数(运行时)
易错点
- 多态强调"不同对象"对"同名函数"的不同响应
🔄 举一反三
- C++ 中实现运行时多态的关键机制是( )
- A. 函数重载
- B. 模板
- C. 虚函数
- D. 内联函数
查看练习答案与解析
答案:C 解析:虚函数 + 基类指针/引用实现运行时多态。函数重载和模板是编译时多态。
- 要将类 A 说明是类 B 的虚基类,正确的描述是( )
- A.
class virtual B:public A - B.
class B:virtual public A - C.
virtual class B:public A - D.
class B:public A virtual
- A.
查看答案与解析
答案:B
解析: 虚基类的正确声明格式:class 派生类 : virtual 继承方式 基类
class B : virtual public A { }; // ✅ A 是 B 的虚基类virtual 关键字放在继承方式之前。
难度: ⭐ 考点: #虚基类 #virtual继承
💡 学习锦囊
📖 相关公式与知识点:
- 虚基类声明:
class D : virtual public B { }; - 虚基类用于解决菱形继承中的二义性问题
- 虚基类由最远派生类负责初始化
易错点
virtual在继承方式之前,不是之后- 虚基类 ≠ 抽象类
🔄 举一反三
- 下列关于虚基类的描述,正确的是( )
- A. 虚基类就是抽象类
- B. 虚基类用于解决多继承中的成员二义性
- C. 虚基类不能有数据成员
- D. 虚基类的构造函数不会被调用
查看练习答案与解析
答案:B 解析:虚基类解决菱形继承中共同基类成员的多份拷贝问题。
- 标准模板库(STL)所涉及的 4 个最主要的基本组件是( )
- A. 容器、迭代器、算法、函数模板
- B. 类模板、运算符重载函数、容器、算法
- C. 容器、算法、迭代器、函数对象
- D. 类、对象、迭代器、函数
查看答案与解析
答案:C
解析: STL 的四大核心组件:
- 容器(Containers):存储数据的模板类(vector、list、map 等)
- 算法(Algorithms):操作数据的模板函数(sort、find、copy 等)
- 迭代器(Iterators):连接容器和算法的桥梁,类似指针
- 函数对象(Functors):重载了
operator()的类对象
难度: ⭐ 考点: #STL #容器 #迭代器 #算法
💡 学习锦囊
📖 相关公式与知识点:
- STL 四大组件:容器、算法、迭代器、函数对象
- 容器:vector、list、deque、set、map 等
- 迭代器:输入、输出、前向、双向、随机访问
- 函数对象:重载
operator()的类
易错点
- 函数模板不是 STL 的四大组件之一
- 函数对象(Functor)≠ 函数指针
🔄 举一反三
- STL 中连接容器和算法的桥梁是( )
- A. 容器
- B. 算法
- C. 迭代器
- D. 函数对象
查看练习答案与解析
答案:C 解析:迭代器是容器和算法之间的桥梁,使算法可以独立于容器类型工作。
二、程序填空题(每空 2 分,总计 20 分)
- 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
2,1
4,32
#include <iostream>
class A {
int a;
public:
A(int i = 0) { a = i; }
int Geta() { return a; }
};
class B {
A a;
int b;
public:
B(int i = 0, int j = 0) : __(1)__ {}
void display() { cout << a.Geta() << ',' << b << endl; }
};
int main() {
B b[2] = { B(1, 2), B(3, 4) };
for (int i = 0; i < 2; i++)
__(2)__
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
查看答案与解析
答案: (1)a(j), b(i)
解析: 输出 2,1 对应 B(1,2):a.Geta()=2, b=1 → a 用 j=2 初始化,b 用 i=1 初始化 输出 4,3 对应 B(3,4):a.Geta()=4, b=3 → a 用 j=4 初始化,b 用 i=3 初始化
所以初始化列表为 a(j), b(i)。
(2)b[i].display()
解析: 遍历数组调用每个元素的 display() 函数输出结果。
难度: ⭐⭐ 考点: #成员初始化列表 #对象数组 #成员函数调用
💡 学习锦囊
📖 相关公式与知识点:
- 初始化列表:
成员名(初始值) - 对象数组成员访问:
数组名[下标].成员函数() - 分析输出反推初始化参数
思路分析
根据输出反推初始化列表的步骤:
- 观察输出
2,1和4,3 - 对应输入
B(1,2)和B(3,4) - a 的值 = 第二个参数,b 的值 = 第一个参数
- 所以初始化列表为
a(j), b(i)
🔄 举一反三
- 若初始化列表改为
a(i), b(j),则B(1,2)的输出是什么?查看练习答案与解析
答案:
1,2解析:a 用 i=1 初始化,b 用 j=2 初始化。
- 下面程序中 A 是抽象类。请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
B1 called
B2 called2
#include <iostream>
class A {
public:
__(3)__ ;
};
class B1 : public A {
public:
void display() { cout << "B1 called" << endl; }
};
class B2 : public A {
public:
void display() { cout << "B2 called" << endl; }
};
void show( __(4)__ ) {
p->display();
}
int main() {
B1 b1;
B2 b2;
A *p[2] = { &b1, &b2 };
for (int i = 0; i < 2; i++)
show(p[i]);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
查看答案与解析
答案: (3)virtual void display() = 0 (4)A *p
解析:
(3):A 是抽象类,需要声明纯虚函数。display() 在派生类中被重写,所以基类中声明为 virtual void display() = 0。
(4):show 函数接收基类指针 A *p,通过指针调用虚函数 display(),实现多态。
难度: ⭐⭐ 考点: #纯虚函数 #抽象类 #多态
💡 学习锦囊
📖 相关公式与知识点:
- 抽象类 = 含纯虚函数的类
- 纯虚函数:
virtual 返回类型 函数名(参数) = 0; - 多态三要素:继承、虚函数、基类指针/引用
易错点
- 纯虚函数必须有
virtual和= 0 - show 函数的参数类型必须与 p 数组的元素类型一致(A*)
🔄 举一反三
- 若将 show 函数的参数改为
B1 *p,则show(p[1])(p[1] 指向 B2 对象)会怎样?查看练习答案与解析
答案:编译错误,B2* 不能隐式转换为 B1* 解析:B1 和 B2 是兄弟类,指针类型不兼容。
- 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为:
Name:王小明
Grade:902
#include <iostream>
#include <cstring>
class Person {
char name[20];
public:
Person(char *s) { strcpy(name, s); }
void display() { cout << "Name:" << name << endl; }
};
class Student : public Person {
int grade;
public:
Student(char *s, int g) : __(5)__ { grade = g; }
void display() {
__(6)__
cout << "Grade:" << grade << endl;
}
};
int main() {
Student s("王小明", 90);
s.display();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
查看答案与解析
答案: (5)Person(s) (6)Person::display();
解析:
(5):Student 继承自 Person,需要在初始化列表中调用基类构造函数 Person(s) 来初始化 name。
(6):Student::display() 需要先调用基类的 display() 输出姓名,再输出成绩。使用 Person::display() 显式调用基类版本。
难度: ⭐⭐ 考点: #继承 #基类构造函数调用 #作用域解析
💡 学习锦囊
📖 相关公式与知识点:
- 派生类构造函数初始化列表调用基类构造:
派生类(参数) : 基类(参数) { } - 显式调用基类成员:
基类名::成员名 - 派生类对象构造顺序:基类 → 派生类
易错点
- 基类构造函数只能在初始化列表中调用
- 派生类中调用同名基类函数需用
基类名::函数名()
🔄 举一反三
- 若 Student 的构造函数改为
Student(char *s, int g) { Person(s); grade = g; },是否正确?查看练习答案与解析
答案:不正确。
Person(s)在函数体内会创建一个临时 Person 对象,而不是初始化基类部分。 解析:基类构造必须在初始化列表中调用,函数体内的Person(s)是创建临时对象。
- 请在下面程序的横线处填上适当内容,以使程序完整,并使程序的输出为
5。
#include <iostream>
class Integer {
int x;
public:
Integer(int a = 0) { x = a; }
void display() { cout << x << endl; }
__(7)__ ;
};
Integer Max(Integer a, Integer b) {
if ( __(8)__ )
return a;
return b;
}
int main() {
Integer a(3), b(5), c;
c = Max(a, b);
c.display();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
查看答案与解析
答案: (7)friend Integer Max(Integer a, Integer b) (8)a.x > b.x
解析:
(7):Max 函数需要访问 Integer 类的私有成员 x,因此必须声明为友元函数。格式:friend 返回类型 函数名(参数列表);
(8):Max 函数比较两个 Integer 对象的 x 值,返回较大的。条件为 a.x > b.x。
难度: ⭐⭐ 考点: #友元函数 #私有成员访问
💡 学习锦囊
📖 相关公式与知识点:
- 友元函数声明:
friend 返回类型 函数名(参数); - 友元函数可以访问类的私有成员
- 友元关系是单向的、不可传递的
易错点
- 友元函数不是成员函数,不能用
对象.友元函数()调用 - 友元声明放在类内部,但函数定义在类外部
🔄 举一反三
- 若将 Max 改为成员函数
Integer Max(Integer b),应如何实现?查看练习答案与解析
答案:
cppInteger Max(Integer b) { return x > b.x ? *this : b; }1
2
3解析:成员函数可直接访问当前对象的 x,通过
b.x访问参数对象的私有成员(同类对象间可互相访问私有成员)。
- 请在下面的横线处填上适当内容,以使类的定义完整。
class Array {
int *ptr;
int size;
public:
Array() { size = 0; ptr = NULL; }
Array(int n) { size = n; ptr = new int[size]; }
Array( __(9)__ ) { // 复制初始化构造函数
size = a.size;
ptr = new int[size];
for (int i = 0; i < size; i++)
__(10)__ ; // 将源对象的动态数组内容复制到目标对象
}
};2
3
4
5
6
7
8
9
10
11
12
13
查看答案与解析
答案: (9)const Array &a (10)ptr[i] = a.ptr[i]
解析:
(9):拷贝构造函数的参数必须是本类对象的引用,通常加 const 防止修改源对象。格式:const Array &a
(10):深拷贝的核心操作——逐元素复制动态数组内容。ptr[i] = a.ptr[i]
难度: ⭐⭐ 考点: #拷贝构造函数 #深拷贝 #动态内存
💡 学习锦囊
📖 相关公式与知识点:
- 拷贝构造函数:
类名(const 类名 &obj) - 深拷贝:重新分配内存并复制数据
- 浅拷贝:只复制指针值,两个对象指向同一内存
易错点
- 拷贝构造函数参数必须是引用(否则无限递归)
- 含动态内存的类必须实现深拷贝
🔄 举一反三
- 若将拷贝构造函数改为浅拷贝
Array(const Array &a) { ptr = a.ptr; size = a.size; },会出现什么问题?查看练习答案与解析
答案:两个对象的 ptr 指向同一内存,析构时重复释放导致未定义行为。 解析:浅拷贝导致 double free 错误。
三、阅读程序题(每题 6 分,总计 30 分)
- 阅读该程序,给出程序的输出结果。
#include <iostream>
class Test {
private:
int num;
public:
Test(int n = 0) { num = n; num++; }
~Test() { cout << "Destructor is active,number=" << num << endl; }
};
int main() {
Test x[2];
cout << "Exiting main" << endl;
}2
3
4
5
6
7
8
9
10
11
12
13
查看答案与解析
答案:
Exiting main
Destructor is active,number=1
Destructor is active,number=12
3
解析:
Test x[2]:创建 2 个对象的数组,每个调用默认构造函数Test(0):num = 0,然后num++→num = 1- 两个对象的 num 都是 1
cout << "Exiting main" << endl;:输出Exiting mainmain 函数结束,数组 x 析构(逆序):
- x[1] 析构:
Destructor is active,number=1 - x[0] 析构:
Destructor is active,number=1
- x[1] 析构:
难度: ⭐⭐ 考点: #构造函数 #析构函数 #对象数组
💡 学习锦囊
📖 相关公式与知识点:
类名 数组名[n]:创建 n 个对象,每个调用默认构造函数- 析构顺序与构造顺序相反
- 默认参数
n=0使Test()等价于Test(0)
易错点
- 注意
num++使最终值为 1 而非 0 - 析构顺序是逆序的
🔄 举一反三
- 若将
Test x[2]改为Test x[2] = { Test(5), Test(10) },输出是什么?查看练习答案与解析
答案:
Exiting main Destructor is active,number=11 Destructor is active,number=61
2
3解析:x[0].num=5→6,x[1].num=10→11。析构逆序:先 11 后 6。
- 阅读该程序,给出程序的输出结果。
#include <iostream>
class A {
public:
virtual void fun(int data) { cout << "class A:" << data << endl; }
void fun(char *str) { cout << "class A:" << str << endl; }
};
class B : public A {
public:
void fun() { cout << "class B" << endl; }
void fun(int data) { cout << "class B:" << data << endl; }
void fun(char *str) { cout << "class B:" << str << endl; }
};
int main() {
A *pA;
B b;
pA = &b;
pA->fun(1);
pA->fun("Hello");
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
查看答案与解析
答案:
class B:1
class A:Hello2
解析:
pA->fun(1):fun(int) 在基类 A 中声明为 virtual,派生类 B 重写了它。通过基类指针调用虚函数 → 动态联编 → 调用B::fun(int)→ 输出class B:1pA->fun("Hello"):fun(char*) 在基类 A 中没有 virtual 关键字,是普通函数。通过基类指针调用 → 静态联编 → 根据指针静态类型 A* 调用A::fun(char*)→ 输出class A:Hello
关键区别:virtual 决定动态联编 vs 静态联编。
难度: ⭐⭐⭐ 考点: #虚函数 #动态联编 #静态联编 #函数隐藏
💡 学习锦囊
📖 相关公式与知识点:
- 虚函数(virtual)→ 动态联编(运行时根据实际对象类型决定)
- 非虚函数 → 静态联编(编译时根据指针/引用类型决定)
- 派生类中与基类虚函数同名的函数自动成为虚函数
易错点
- 只有声明为 virtual 的函数才走动态联编
- fun(char*) 不是虚函数,所以调用基类版本
🔄 举一反三
- 若将
A::fun(char *str)也声明为virtual,则pA->fun("Hello")输出什么?查看练习答案与解析
答案:
class B:Hello解析:两个 fun 都是虚函数,都走动态联编,调用 B 的版本。
- 阅读该程序,给出程序的输出结果。
#include <iostream>
int main() {
cout.fill('*');
cout.width(10);
cout << "123.45" << endl;
cout.width(8);
cout << "123.45" << endl;
cout.width(4);
cout << "123.45" << endl;
}2
3
4
5
6
7
8
9
10
查看答案与解析
答案:
****123.45
**123.45
123.452
3
解析: 本题考查 cout.fill() 和 cout.width() 的用法。
cout.fill('*'):设置填充字符为*,持续有效直到被重新设置cout.width(10):设置输出宽度为 10,"123.45"占 6 个字符,左侧填充 4 个*→****123.45cout.width(8):宽度 8,"123.45"占 6 个字符,左侧填充 2 个*(fill('*')仍然有效)→**123.45cout.width(4):宽度 4 < 字符串长度 6,不填充 →123.45
注意:fill() 设置的填充字符持续有效,不会因为 width() 的重设而恢复默认。width() 只对下一次输出有效。
难度: ⭐⭐ 考点: #cout.fill #cout.width #格式化输出
💡 学习锦囊
📖 相关公式与知识点:
cout.width(n):设置输出宽度为 n,只对紧接着的一次输出有效cout.fill(ch):设置填充字符为 ch,持续有效直到再次设置- 默认对齐:右对齐,左侧填充
易错点
width()只生效一次,之后恢复默认宽度 0fill()持续生效- 当
width(n)的 n ≤ 实际字符数时,不填充
🔄 举一反三
- 若在第三次输出前调用
cout.fill('0'),第三次输出是什么?查看练习答案与解析
答案:
123.45(width(4) < 6,不填充) 解析:width(4) 小于字符串长度 6,不进行填充。
- 从键盘输入 10,给出程序的输出结果。
#include <iostream>
#include <vector>
#include <iterator>
using namespace std;
int main() {
vector<int> v;
int i, j, n;
cin >> n;
for (i = 1; i <= n; i++) {
if (i % 2 != 0) {
v.push_back(i);
}
}
vector<int>::iterator p;
for (p = v.begin(); p != v.end(); p++)
cout << *p << " ";
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
查看答案与解析
答案:
1 3 5 7 9解析: 输入 n = 10,循环 i 从 1 到 10:
i % 2 != 0筛选奇数:1, 3, 5, 7, 9v.push_back(i)将奇数依次添加到 vector 末尾- 使用迭代器遍历输出所有奇数
难度: ⭐⭐ 考点: #vector #迭代器 #push_back
💡 学习锦囊
📖 相关公式与知识点:
vector<int> v:动态数组v.push_back(x):在末尾添加元素- 迭代器遍历:
for (p = v.begin(); p != v.end(); p++) *p解引用获取元素值
易错点
push_back添加元素,不是替换- 迭代器
end()指向最后一个元素之后的位置
🔄 举一反三
- 若将条件改为
if (i % 2 == 0),输入 10,输出是什么?查看练习答案与解析
答案:
2 4 6 8 10解析:筛选偶数 2, 4, 6, 8, 10。
- 阅读该程序,给出程序的输出结果。
#include <iostream>
using namespace std;
class Sample {
private:
int i;
static int count;
public:
Sample();
void display();
};
Sample::Sample() {
i = 0;
count++;
}
void Sample::display() {
cout << "i=" << i++ << ",count=" << count << endl;
}
int Sample::count = 0;
int main() {
Sample a, b;
a.display();
b.display();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
查看答案与解析
答案:
i=0,count=2
i=0,count=22
解析:
Sample a, b:创建两个对象,每个调用构造函数:- a:
i=0, count++→ count=1 - b:
i=0, count++→ count=2 - count 是静态成员,所有对象共享
- a:
a.display():i=0(a 的 i),然后i++→ a.i=1count=2(静态成员,值为 2)
b.display():i=0(b 的 i),然后i++→ b.i=1count=2(静态成员,值仍为 2)
关键:静态成员 count 在所有对象间共享,值为构造的对象总数 2。
难度: ⭐⭐ 考点: #静态数据成员 #静态成员初始化
💡 学习锦囊
📖 相关公式与知识点:
- 静态成员属于类,所有对象共享一份
- 静态成员初始化:
类型 类名::成员名 = 值;(类体外) - 静态成员在对象创建前就已存在
易错点
- 静态成员 count 的值是 2(两个对象都构造完毕),不是 1
- 每个对象有自己的 i,但共享同一个 count
🔄 举一反三
- 若在
b.display()后再调用a.display(),输出什么?查看练习答案与解析
答案:
i=1,count=2解析:a.i 在第一次 display() 后变为 1,count 仍为 2。
四、编程题(每题 15 分,总计 30 分)
- 按下列要求编程,实现类的定义,并在主函数中测试这个类。
定义一个描述日期的类 Date,包括的数据成员有年(year)、月(month)和日(day),并实现如下功能函数: (1)日期对象初始化; (2)设置日期; (3)以 year/month/day 形式输出日期; (4)判断闰年。
查看答案与解析
答案:
#include <iostream>
using namespace std;
class Date {
private:
int year, month, day;
public:
Date(int y = 1900, int m = 1, int d = 1) {
year = y;
month = m;
day = d;
}
void setDate(int y, int m, int d) {
year = y;
month = m;
day = d;
}
void outputDate() {
cout << year << "/" << month << "/" << day << endl;
}
bool isLeapYear() {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
};
int main() {
Date d;
d.outputDate(); // 1900/1/1
d.setDate(2015, 3, 5);
d.outputDate(); // 2015/3/5
if (d.isLeapYear())
cout << "a leap year." << endl;
else
cout << "not a leap year." << endl;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
解析:
(1)日期对象初始化:使用带默认参数的构造函数 Date(int y = 1900, int m = 1, int d = 1),支持无参和有参初始化。
(2)设置日期:setDate() 函数修改年、月、日。
(3)输出日期:outputDate() 以 year/month/day 格式输出。
(4)判断闰年:isLeapYear() 函数,闰年规则:
- 能被 4 整除且不能被 100 整除,或能被 400 整除
- 2015 年不是闰年,输出
not a leap year.
难度: ⭐⭐ 考点: #类定义 #构造函数 #成员函数 #闰年判断
💡 学习锦囊
📖 相关公式与知识点:
- 闰年判断:
(year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) - 带默认参数的构造函数可兼作默认构造函数
- 类设计步骤:确定数据成员 → 设计构造函数 → 实现功能函数
易错点
- 1900 年不是闰年(能被 100 整除但不能被 400 整除)
- 2000 年是闰年(能被 400 整除)
🔄 举一反三
- 在 Date 类中增加一个函数
int daysOfMonth(),返回当前月份的天数(考虑闰年)。查看练习答案与解析
答案:
cppint daysOfMonth() { int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (month == 2 && isLeapYear()) return 29; return days[month]; }1
2
3
4
5解析:使用数组存储每月天数,2 月需特殊处理闰年。
- 根据下列 Vector 类定义,编程完成 Vector 类的具体实现:
class Vector {
friend ostream &operator << (ostream &out, const Vector &v);
private:
int *data;
int size;
public:
Vector();
Vector(int a[], int n);
Vector(const Vector &s);
~Vector();
Vector &operator=(const Vector &v);
int &operator[](int index);
};2
3
4
5
6
7
8
9
10
11
12
13
查看答案与解析
答案:
#include <iostream>
using namespace std;
class Vector {
friend ostream &operator << (ostream &out, const Vector &v);
private:
int *data;
int size;
public:
Vector();
Vector(int a[], int n);
Vector(const Vector &s);
~Vector();
Vector &operator=(const Vector &v);
int &operator[](int index);
};
Vector::Vector() {
data = NULL;
size = 0;
}
Vector::Vector(int a[], int n) {
size = n;
data = new int[size];
for (int i = 0; i < size; i++)
data[i] = a[i];
}
Vector::Vector(const Vector &s) {
size = s.size;
data = new int[size];
for (int i = 0; i < size; i++)
data[i] = s.data[i];
}
Vector::~Vector() {
if (data != NULL)
delete[] data;
}
Vector &Vector::operator=(const Vector &v) {
if (this == &v)
return *this;
if (size != v.size) {
delete[] data;
size = v.size;
data = new int[size];
}
for (int i = 0; i < size; i++)
data[i] = v.data[i];
return *this;
}
int &Vector::operator[](int index) {
return data[index];
}
ostream &operator << (ostream &out, const Vector &v) {
for (int i = 0; i < v.size; i++)
out << v.data[i] << ";";
return out;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
解析:
(1)默认构造函数 Vector():初始化 data 为 NULL,size 为 0。
(2)带参构造函数 Vector(int a[], int n):从数组初始化,动态分配内存并复制数据。
(3)拷贝构造函数 Vector(const Vector &s):深拷贝,重新分配内存并复制数据。必须深拷贝,否则两个对象的 data 指向同一内存,析构时重复释放。
(4)析构函数 ~Vector():释放动态分配的内存 delete[] data。注意检查 data 是否为 NULL。
(5)赋值运算符 operator=:
- 自赋值检查:
if (this == &v) return *this; - 大小不同时释放旧内存,重新分配
- 逐元素复制
- 返回
*this支持链式赋值
(6)下标运算符 operator[]:返回引用,支持读写操作。
(7)输出运算符 operator<<:友元函数,访问私有成员,输出向量元素。
难度: ⭐⭐⭐ 考点: #动态内存管理 #深拷贝 #运算符重载 #拷贝构造函数 #三法则
💡 学习锦囊
📖 相关公式与知识点:
- 三法则(Rule of Three):如果需要自定义析构函数、拷贝构造函数或赋值运算符中的任何一个,通常需要自定义全部三个
- 深拷贝 vs 浅拷贝:含动态内存的类必须深拷贝
- 自赋值检查:
if (this == &v)防止自赋值导致错误 operator[]返回引用才能支持v[i] = 10这样的写操作
思路分析
实现含动态内存的类的关键步骤:
- 构造函数:动态分配内存,复制数据
- 析构函数:释放动态内存(检查 NULL)
- 拷贝构造:深拷贝,重新分配内存
- 赋值运算:先释放旧内存,再深拷贝,注意自赋值检查
- 下标运算:返回引用支持读写
- 输出运算:友元函数访问私有成员
易错点
- 赋值运算符必须检查自赋值,否则
delete[] data后再访问v.data会出错 - 拷贝构造函数必须深拷贝,浅拷贝会导致析构时重复释放内存
- 赋值运算符中先释放旧内存再分配新内存
operator[]返回引用才能支持写操作- 析构函数中
delete[] NULL是安全的,但检查一下更清晰
🔄 举一反三
- 若在 Vector 类中增加一个
resize(int newSize)函数,应如何实现?查看练习答案与解析
答案:
cppvoid resize(int newSize) { int *newData = new int[newSize]; int copySize = (newSize < size) ? newSize : size; for (int i = 0; i < copySize; i++) newData[i] = data[i]; delete[] data; data = newData; size = newSize; }1
2
3
4
5
6
7
8
9解析:分配新内存,复制数据,释放旧内存,更新指针和大小。
- 若将拷贝构造函数改为浅拷贝(
data = s.data; size = s.size;),程序会出现什么问题?查看练习答案与解析
答案:两个对象的 data 指向同一内存,析构时同一内存被
delete[]两次,导致未定义行为(double free)。 解析:这就是为什么含动态内存的类必须实现深拷贝。